`define BURST_LENGTH 2:0
`define CAS_LATENCY  6:4

import "DPI-C" function void sdram_read(input byte chipnum, input int addr, output int data);
import "DPI-C" function void sdram_write(input byte chipnum, input int addr, input int data, input int mask);

module sdram #(
  parameter CHIP = 0
)(
  input        clk,
  input        cke,
  input        cs,
  input        ras,
  input        cas,
  input        we,
  input [13:0] a,    // 地址
  input [ 1:0] ba,   // 存储体地址
  input [ 1:0] dqm,  // 数据掩码
  inout [15:0] dq    // 数据
);

  localparam CMD_NOP           = 4'b0111;
  localparam CMD_ACTIVE        = 4'b0011;
  localparam CMD_READ          = 4'b0101;
  localparam CMD_WRITE         = 4'b0100;
  localparam CMD_TERMINATE     = 4'b0110;
  localparam CMD_PRECHARGE     = 4'b0010;
  localparam CMD_REFRESH       = 4'b0001;
  localparam CMD_LOAD_MODE     = 4'b0000;

  wire [15:0] dout;
  wire [15:0] din;
  wire        den;
  wire        chvd;
  wire        rvalid;
  wire        wvalid;

  wire resetn;
  wire [3:0] cmd;
  wire [2:0] burst_length;
  wire [2:0] cas_latency;

  wire [1:0] wmask;

  reg [31:0] rdata;
  reg [15:0] wdata;

  reg [13:0] addr_row [3:0];
  reg [13:0] addr_col;
  reg [ 1:0] addr_bank;

  reg [ 1:0] mask;

  reg [13:0] mode_reg;

  reg [ 2:0] counter;

  assign resetn = cke;
  assign cmd = {cs, ras, cas, we};
  assign burst_length = mode_reg[`BURST_LENGTH];
  assign cas_latency  = mode_reg[`CAS_LATENCY];

  assign wmask = ( CHIP == 0 || CHIP == 2 ) ? mask :
                 ( CHIP == 1 || CHIP == 3)  ? dqm  : 2'd0;

  // 1. 命令解码
  // 2. 数据传输

  typedef enum [2:0] {STATE_IDLE, STATE_ACTIVE, STATE_READ, STATE_WRITE0, STATE_WRITE1} state_t;
  state_t curr_state, next_state;

  always @(posedge clk or negedge resetn) begin
    if (!resetn) curr_state <= STATE_IDLE;
    else curr_state <= next_state;
  end

  always @(*) begin
    case(curr_state)
      STATE_IDLE :
        if (cmd == CMD_ACTIVE) next_state = STATE_ACTIVE;
        else next_state = STATE_IDLE;

      STATE_ACTIVE :
        if (cmd == CMD_READ) next_state = STATE_READ;
        else if (cmd == CMD_WRITE) next_state = STATE_WRITE0;
        else next_state = STATE_ACTIVE;

      STATE_READ :
        if (cmd == CMD_ACTIVE) next_state = STATE_ACTIVE;
        else if (cmd == CMD_WRITE) next_state = STATE_WRITE0;
        else next_state = STATE_READ;

      STATE_WRITE0 :
        if (cmd == CMD_ACTIVE) next_state = STATE_ACTIVE;
        else if (cmd == CMD_READ) next_state = STATE_READ;
        else if (cmd == CMD_NOP) next_state = STATE_WRITE1;
        else next_state = STATE_WRITE0;

      STATE_WRITE1 :
        if (cmd == CMD_ACTIVE) next_state = STATE_ACTIVE;
        else if (cmd == CMD_WRITE) next_state = STATE_WRITE0;
        else if (cmd == CMD_READ) next_state = STATE_READ;
        else next_state = STATE_WRITE1;

      default:
        next_state = STATE_IDLE;

    endcase
  end

  // get addresss
  always @(posedge clk or negedge resetn) begin
    if (!resetn) begin
      addr_row[0]  <= 14'd0;
      addr_row[1]  <= 14'd0;
      addr_row[2]  <= 14'd0;
      addr_row[3]  <= 14'd0;
      addr_col  <= 14'd0;
      addr_bank <= 2'd0;
    end
    else if (cmd == CMD_ACTIVE) begin
      addr_row[ba] <= a;
      addr_col     <= addr_col;
      addr_bank    <= ba;
    end
    else if (cmd == CMD_READ || cmd ==  CMD_WRITE) begin
      addr_row  <= addr_row;
      addr_col  <= a;
      addr_bank <= ba;
    end
  end

  // set mode_reg
  always @(posedge clk or negedge resetn) begin
    if (!resetn) mode_reg <= 14'd0;
    else if (cmd == CMD_LOAD_MODE) mode_reg  <= a;
  end

  // read data
  always @(posedge clk or negedge resetn) begin
    if (!resetn) rdata = 32'd0;
    else if (cmd == CMD_READ && rvalid) sdram_read(CHIP, {3'd0, addr_row[ba], ba, a[12:0]}, rdata);
  end

  // get mask and write data
  always @(posedge clk or negedge resetn) begin
    if (!resetn) begin
      mask  <= 2'd0;
      wdata <= 16'd0;
    end
    else if (cmd == CMD_WRITE) begin
      mask  <= dqm;
      wdata <= din;
    end
  end

  // write data
  always @(posedge clk) begin
    if (curr_state == STATE_WRITE0 && next_state == STATE_WRITE1 && wmask != 3 && wvalid) sdram_write(CHIP, {3'd0, addr_row[addr_bank], addr_bank, addr_col[12:0]}, {16'd0, wdata}, {30'd0, wmask});
  end

  // counter
  always @(posedge clk or negedge resetn) begin
    if (!resetn) counter <= 3'd0;
    else begin
      if (cmd == CMD_READ) counter <= 3'd0;
      else if (counter == 3) counter <= counter;
      else counter <= counter + 1;
    end
  end

  assign rvalid = (CHIP == 0 || CHIP == 1) ? addr_row[ba][13] == 0 :
                  (CHIP == 2 || CHIP == 3) ? addr_row[ba][13] == 1 : 0;
  assign wvalid = chvd;

  assign chvd   = (CHIP == 0 || CHIP == 1) ? addr_row[addr_bank][13] == 0 :
                 (CHIP == 2 || CHIP == 3) ? addr_row[addr_bank][13] == 1 : 0;
  // assign den  = (curr_state == STATE_READ) && (counter == 1 || counter == 2);
  assign den    = (curr_state == STATE_READ) && (counter == 1) && chvd;
  assign din    = dq;
  assign dout   = counter != 1 ? 16'd0 : rdata[15:0];
  assign dq     = den ? dout : 16'bz;
  // assign dq = 16'bz;

endmodule
